﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.IO;

namespace FlatAssembler
{
	#region Function headers
	[Flags, Serializable]
	public enum FileAccessMode: int
	{
		Read = unchecked((int)0x80000000),
		Write = 0x40000000,
	}

	[Flags, Serializable]
	public enum FileShareMode: int
	{
		Nothing = 0,
		Read = 1,
		Write = 2,
		Delete = 4,
	}

	[Flags, Serializable]
	public enum FileOpenMode: int
	{
		/// <summary>
		/// Ошибка, если файд уже существует
		/// </summary>
		CreateNew = 1,
		/// <summary>
		/// Пересоздаёт файл, если он уже существует
		/// </summary>
		CreateAlways = 2,
		OpenExisting = 3,
		OpenAlways = 4,
		TruncateExisting = 5,
	}

	[Flags, Serializable]
	public enum FileMoveMode: int
	{
		Begin= 0,
		Current = 1,
		End = 2,
	}

	[Flags, Serializable]
	public enum MemoryAllocationType: int
	{
		Commit             = 0x1000,
		Reserve            = 0x2000,
		Decommit           = 0x4000,
		Release            = 0x8000,
		Free               = 0x10000,
		Private            = 0x20000,
		Mapped             = 0x40000,
		Reset              = 0x80000,
		TopDown           = 0x100000,
	}

	[Flags, Serializable]
	public enum MemoryProtectionType: int
	{
		NoAccess          = 1,
		ReadOnly          = 2,
		ReadWrite         = 4,
		WriteCopy         = 8,
		Execute           = 0x10,
		ExecuteRead       = 0x20,
		ExecuteReadWrite  = 0x40,
		ExecuteWriteCopy  = 0x80,
		Guard             = 0x100,
		NoCache           = 0x200,
	}

	[Serializable]
	public enum StdFileHandle: int
	{
		Input = -10,
		Output = -11,
		Error = -12,
	}

	/// <summary>
	/// Открытие файла
	/// </summary>
	/// <param name="fileName">Имя файла</param>
	/// <param name="access">Режим доступа</param>
	/// <param name="share">Режим разделения доступа</param>
	/// <param name="security">Блок данных безопасности. Не поддерживается (должен быть 0)</param>
	/// <param name="openMode">Режим открытия файла</param>
	/// <param name="flags">Флаги. Не поддерживаются (должны быть равны 0)</param>
	/// <param name="templateFile">Файл - шаблон атрибутов. Не поддерживается (должен быть равен 0)</param>
	/// <returns>Дескриптор файла</returns>
	public delegate IntPtr CreateFile(byte[] fileName, FileAccessMode access, FileShareMode share, IntPtr security, 
		FileOpenMode openMode, int flags, IntPtr templateFile);

	/// <summary>
	/// Чтение из файла
	/// </summary>
	/// <param name="file">Дескриптор файла</param>
	/// <param name="buffer">Целевой буфер</param>
	/// <param name="readBytes">Число байт для чтения</param>
	/// <param name="done">Число прочитанных байт</param>
	/// <param name="overlapped">Не поддерживается</param>
	/// <returns>Успешность операции</returns>
	public delegate bool ReadFile(IntPtr file, IntPtr buffer, int readBytes, out int done, IntPtr overlapped);

	/// <summary>
	/// Запись в файл
	/// </summary>
	/// <param name="file">Дескриптор файла</param>
	/// <param name="buffer">Буфер-источник</param>
	/// <param name="writeBytes">Число байт для записи</param>
	/// <param name="done">Число записанных байт</param>
	/// <param name="overlapped">Не поддерживается</param>
	/// <returns>Успешность операции</returns>
	public delegate bool WriteFile(IntPtr file, IntPtr buffer, int writeBytes, out int done, IntPtr overlapped);

	/// <summary>
	/// Закрытие дескриптора
	/// </summary>
	/// <param name="handle">Дескриптор</param>
	/// <returns>Успешность операции</returns>
	public delegate bool CloseHandle(IntPtr handle);

	/// <summary>
	/// Перемещение файлового указателя
	/// </summary>
	/// <param name="file">Дескриптор файла</param>
	/// <param name="offset">Смещение</param>
	/// <param name="offsetHigh">Верхние биты смещение. Не поддерживается (должен быть равен 0)</param>
	/// <param name="offsetBase">Точка отсчёта</param>
	/// <returns>Новая позиция</returns>
	public delegate int SetFilePointer(IntPtr file, int offset, IntPtr offsetHigh, FileMoveMode offsetBase);

	public delegate IntPtr GetCommandLine();

	/// <summary>
	/// Получение значения переменной окружения
	/// </summary>
	/// <param name="varname">Имя переменной окружения</param>
	/// <param name="varvalue">Буфер для записи значения</param>
	/// <param name="size">Размер буфера в символах</param>
	/// <returns>Длина значения переменной. В случае ошибки - 0.</returns>
	public delegate int GetEnvironmentVariable(byte[] varname, IntPtr varvalue, int size);

	public delegate IntPtr GetStdHandle(StdFileHandle fileHandle);

	public delegate IntPtr VirtualAlloc(IntPtr address, int size, MemoryAllocationType alloc, MemoryProtectionType protect);

	/// <summary>
	/// Время с момента запуска системы в миллисекундах
	/// </summary>
	public delegate int GetTickCount();

	[Serializable, StructLayout(LayoutKind.Sequential)]
	public struct WindowsSystemTime
	{
		public ushort Year;
		public ushort Month;
		public ushort DayOfWeek;
		public ushort Day;
		public ushort Hour;
		public ushort Minute;
		public ushort Second;
		public ushort Milliseconds;
	}

	public delegate void GetSystemTime(out WindowsSystemTime time);

	[Serializable, StructLayout(LayoutKind.Sequential)]
	public struct WindowsGlobalMemoryStatus
	{
		/// <summary>
		/// Длина структуры
		/// </summary>
		public int Length;

		/// <summary>
		/// Общая загруженность физической памяти в процентах
		/// </summary>
		public int Load;

		public int TotalPhysical;
		public int AvailablePhysical;
		public int TotalPageFile;
		public int AvailPageFile;
		/// <summary>
		/// Размер пользовательской части памяти
		/// </summary>
		public int TotalVirtual;
		/// <summary>
		/// Свободная память процесса
		/// </summary>
		public int AvailVirtual;
	}

	public delegate void GlobalMemoryStatus(out WindowsGlobalMemoryStatus status);
	#endregion Function headers
	/// <remarks>
	/// How to use this project:
	/// 1. download flat assembler from http://flatassembler.net/
	/// 2. add fasm.exe to your path
	/// 3. add environment variable FasmSource pointing to fasm\source directory (needs finishing backslash)
	/// 4. create directory named DLL under fasm\source
	/// Compile the project
	/// 
	/// NOTE: only 32bit Windows is supported now (but maybe wine will help you under *nix)
	/// </remarks>
	class FlatAssembler
	{
		static void Main(string[] args)
		{
			Assemble();
		}

		[DllImport("fasm.dll")]
		public static extern int Assemble();
	}

	static class Hacks
	{
		static string FasmString(this byte[] ansiString)
		{
			return Encoding.Default.GetString(ansiString);
		}

		static Stream Stream(this IntPtr handle)
		{
			Stream result = null;
			if (openedFiles.TryGetValue(handle, out result)) return result;
			else return null;
		}

		static Dictionary<IntPtr, Stream> openedFiles = new Dictionary<IntPtr, Stream>();
		static readonly Random intPtrGen = new Random();

		static IntPtr CreateFileDescriptor()
		{
			IntPtr result;
			lock (openedFiles)
			{
				do
				{
					result = new IntPtr(intPtrGen.Next());
				} while (openedFiles.ContainsKey(result));
				openedFiles.Add(result, null);
			}
			return result;
		}

		public static IntPtr CreateFile(byte[] fileName, FileAccessMode access, FileShareMode share, IntPtr security,
			FileOpenMode openMode, int flags, IntPtr templateFile)
		{
			if (security != IntPtr.Zero) throw new NotSupportedException();
			if (templateFile != IntPtr.Zero) throw new NotSupportedException();
			var file = fileName.FasmString();
			Stream stream;
			if (access == FileAccessMode.Read)
			{
				if (share != FileShareMode.Read) throw new NotSupportedException();
				if (openMode != FileOpenMode.OpenExisting) throw new NotSupportedException();
				stream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read);

			} else
				if (access == FileAccessMode.Write)
				{
					if (share != FileShareMode.Read) throw new NotSupportedException();
					if (openMode != FileOpenMode.CreateAlways) throw new NotSupportedException();
					stream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.Read);
				} else throw new NotSupportedException();
			var ptr = CreateFileDescriptor();
			openedFiles[ptr] = stream;
			return ptr;
		}

		public static bool ReadFile(IntPtr file, [Out] byte[] buffer, int readBytes, out int done, IntPtr overlapped)
		{
			if (overlapped != IntPtr.Zero) throw new NotSupportedException();
			var stream = file.Stream();
			done = -1;
			if (stream == null) return false;
			try
			{
				done = stream.Read(buffer, 0, readBytes);
				return true;
			} catch (IOException)
			{
				return false;
			}
		}

		public static bool WriteFile(IntPtr file, [Out] byte[] buffer, int writeBytes, out int done, IntPtr overlapped)
		{
			if (overlapped != IntPtr.Zero) throw new NotSupportedException();
			var stream = file.Stream();
			done = -1;
			if (stream == null) return false;
			try
			{
				stream.Write(buffer, 0, writeBytes);
				done = writeBytes;
				return true;
			} catch (IOException)
			{
				return false;
			}
		}

		public static bool CloseHandle(IntPtr handle)
		{
			lock (openedFiles)
			{
				Stream stream;
				if (openedFiles.TryGetValue(handle, out stream))
					stream.Close();
				return openedFiles.Remove(handle);
			}
		}

		public static int SetFilePointer(IntPtr file, int offset, IntPtr offsetHigh, FileMoveMode offsetBase)
		{
			var stream = file.Stream();
			if (offsetHigh != IntPtr.Zero) throw new NotSupportedException();
			SeekOrigin origin;
			switch(offsetBase)
			{
			case FileMoveMode.Begin:
				origin = SeekOrigin.Begin;
				break;
			case FileMoveMode.Current:
				origin = SeekOrigin.Current;
				break;
			case FileMoveMode.End:
				origin = SeekOrigin.End;
				break;
			default:
				throw new NotSupportedException();
			}
			return (int)stream.Seek(offset, origin);
		}

		static IntPtr commandLine = Marshal.StringToHGlobalAnsi(Environment.CommandLine);

		public static IntPtr GetCommandLine()
		{
			return commandLine;
		}

		public static int GetEnvironmentVariable(byte[] varname, IntPtr varvalue, int size)
		{
			var name = varname.FasmString();
			var bytes = Encoding.Default.GetBytes(Environment.GetEnvironmentVariable(name);
			if (bytes.Length >= size) return bytes.Length + 1;
			Marshal.Copy(bytes, 0, varvalue, bytes.Length);
			return bytes.Length;
		}

		static Hacks()
		{
			stdIn = CreateFileDescriptor();
			stdOut = CreateFileDescriptor();
			stdErr = CreateFileDescriptor();
			openedFiles[stdIn] = Console.OpenStandardInput();
			openedFiles[stdOut] = Console.OpenStandardOutput();
			openedFiles[stdErr] = Console.OpenStandardError();
		}

		static readonly IntPtr stdOut, stdIn, stdErr;


		public static IntPtr GetStdHandle(StdFileHandle fileHandle)
		{
			switch (fileHandle)
			{
			case StdFileHandle.Error:
				return stdErr;
			case StdFileHandle.Input:
				return stdIn;
			case StdFileHandle.Output:
				return stdOut;
			default:
				throw new NotSupportedException();
			}
		}

		public static IntPtr VirtualAlloc(IntPtr address, int size, MemoryAllocationType alloc, MemoryProtectionType protect)
		{

		}

		/// <summary>
		/// Время с момента запуска системы в миллисекундах
		/// </summary>
		public delegate int GetTickCount();
	}
}
